/**
 * Copyright (c) 2006-2008, Alexander Potochkin
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials provided
 *     with the distribution.
 *   * Neither the name of the JXLayer project nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.jdesktop.jxlayer.plaf;

import org.jdesktop.jxlayer.JXLayer;
import org.jdesktop.jxlayer.plaf.item.LayerItem;

import javax.accessibility.Accessible;
import javax.swing.*;
import javax.swing.plaf.ComponentUI;
import java.awt.*;
import java.awt.event.*;

/**
 * The base class for all {@link JXLayer}'s UI delegates.
 * <p/>
 * {@link #paint(java.awt.Graphics, javax.swing.JComponent)} method performes the
 * painting of the {@code JXLayer}
 * and {@link #eventDispatched(AWTEvent, JXLayer)} method is notified
 * about any input or focus events which have been generated by a {@code JXLayer}
 * or any of its subcomponents.
 * <p/>
 * The {@code LayerUI} is different from UI delegates of the other components,
 * because it is LookAndFeel independent and is not updated by default when
 * the system LookAndFeel is changed.
 * <p/>
 * The subclasses of {@code LayerUI} can either be stateless and shareable
 * by multiple {@code JXLayer}s or not shareable.
 *
 * @see JXLayer#setUI(LayerUI)
 * @see AbstractLayerUI
 */
public abstract class LayerUI<V extends JComponent>
        extends ComponentUI implements LayerItem {

    /**
     * Paints the specified component.
     * Subclasses should override this method and use
     * the specified {@code Graphics} object to
     * render the content of the component.
     * <p/>
     * <b>Note:</b> Subclasses can safely cast the passed component
     * to the {@code JXLayer<V>} <br/> and the passed {@code Graphics}
     * to the {@code Graphics2D} instance.
     *
     * @param g the {@code Graphics} context in which to paint;
     * this object can be safely cast to the {@code Graphics2D} instance.
     * @param c the component being painted;
     * it can be safely cast to the {@code JXLayer<V>}
     */
    @Override
    public void paint(Graphics g, JComponent c) {
        super.paint(g, c);
    }

    /**
     * Dispatches all input and focus events from the {@link JXLayer}
     * and <b>all it subcomponents</b> to this {@code LayerUI}, 
     * when {@link LayerUI#isEnabled()} returns {@code true}.
     *
     * @param event the event to be dispatched
     * @param l the layer this LayerUI is set to
     */
    public void eventDispatched(AWTEvent event, JXLayer<V> l){
    }

    /**
     * Returns the bitmap of event mask to receive by this {@code LayerUI}
     * and all its {@code JXLayer}s
     * <p/>
     * It means that {@link #eventDispatched(AWTEvent, JXLayer)} method
     * will only receive events that match the event mask. By default the mask 
     * includes mouse, mouse motion, mouse wheel, keyboard and focus events.
     *  
     * @return the bitmask of event types to receive for this {@code LayerUI}
     */
    public long getLayerEventMask() {
        return AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK
                | AWTEvent.MOUSE_WHEEL_EVENT_MASK 
                | AWTEvent.KEY_EVENT_MASK | AWTEvent.FOCUS_EVENT_MASK;
    }

    /**
     * Returns {@code true} if the events with the passed {@code id}
     * are enabled for this {@code LayerUI}
     *  
     * @param id id of the event to be checked
     * @return {@code true} if the events with the the passed {@code id}
     * are enabled for this {@code JXLayer}, otherwise returns {@code false}
     */
    public boolean isEventEnabled(int id) {
        long eventMask = getLayerEventMask();
        return (((eventMask & AWTEvent.COMPONENT_EVENT_MASK) != 0 &&
                id >= ComponentEvent.COMPONENT_FIRST &&
                id <= ComponentEvent.COMPONENT_LAST)
                || ((eventMask & AWTEvent.CONTAINER_EVENT_MASK) != 0 &&
                id >= ContainerEvent.CONTAINER_FIRST &&
                id <= ContainerEvent.CONTAINER_LAST)
                || ((eventMask & AWTEvent.FOCUS_EVENT_MASK) != 0 &&
                id >= FocusEvent.FOCUS_FIRST &&
                id <= FocusEvent.FOCUS_LAST)
                || ((eventMask & AWTEvent.KEY_EVENT_MASK) != 0 &&
                id >= KeyEvent.KEY_FIRST &&
                id <= KeyEvent.KEY_LAST)
                || ((eventMask & AWTEvent.MOUSE_WHEEL_EVENT_MASK) != 0 &&
                id == MouseEvent.MOUSE_WHEEL)
                || ((eventMask & AWTEvent.MOUSE_MOTION_EVENT_MASK) != 0 &&
                (id == MouseEvent.MOUSE_MOVED ||
                        id == MouseEvent.MOUSE_DRAGGED))
                || ((eventMask & AWTEvent.MOUSE_EVENT_MASK) != 0 &&
                id != MouseEvent.MOUSE_MOVED &&
                id != MouseEvent.MOUSE_DRAGGED &&
                id != MouseEvent.MOUSE_WHEEL &&
                id >= MouseEvent.MOUSE_FIRST &&
                id <= MouseEvent.MOUSE_LAST)
                || ((eventMask & AWTEvent.WINDOW_EVENT_MASK) != 0 &&
                id >= WindowEvent.WINDOW_FIRST &&
                id <= WindowEvent.WINDOW_LAST)
                || ((eventMask & AWTEvent.ACTION_EVENT_MASK) != 0 &&
                id >= ActionEvent.ACTION_FIRST &&
                id <= ActionEvent.ACTION_LAST)
                || ((eventMask & AWTEvent.ADJUSTMENT_EVENT_MASK) != 0 &&
                id >= AdjustmentEvent.ADJUSTMENT_FIRST &&
                id <= AdjustmentEvent.ADJUSTMENT_LAST)
                || ((eventMask & AWTEvent.ITEM_EVENT_MASK) != 0 &&
                id >= ItemEvent.ITEM_FIRST &&
                id <= ItemEvent.ITEM_LAST)
                || ((eventMask & AWTEvent.TEXT_EVENT_MASK) != 0 &&
                id >= TextEvent.TEXT_FIRST &&
                id <= TextEvent.TEXT_LAST)
                || ((eventMask & AWTEvent.INPUT_METHOD_EVENT_MASK) != 0 &&
                id >= InputMethodEvent.INPUT_METHOD_FIRST &&
                id <= InputMethodEvent.INPUT_METHOD_LAST)
                || ((eventMask & AWTEvent.PAINT_EVENT_MASK) != 0 &&
                id >= PaintEvent.PAINT_FIRST &&
                id <= PaintEvent.PAINT_LAST)
                || ((eventMask & AWTEvent.INVOCATION_EVENT_MASK) != 0 &&
                id >= InvocationEvent.INVOCATION_FIRST &&
                id <= InvocationEvent.INVOCATION_LAST)
                || ((eventMask & AWTEvent.HIERARCHY_EVENT_MASK) != 0 &&
                id == HierarchyEvent.HIERARCHY_CHANGED)
                || ((eventMask & AWTEvent.HIERARCHY_BOUNDS_EVENT_MASK) != 0 &&
                (id == HierarchyEvent.ANCESTOR_MOVED ||
                        id == HierarchyEvent.ANCESTOR_RESIZED))
                || ((eventMask & AWTEvent.WINDOW_STATE_EVENT_MASK) != 0 &&
                id == WindowEvent.WINDOW_STATE_CHANGED)
                || ((eventMask & AWTEvent.WINDOW_FOCUS_EVENT_MASK) != 0 &&
                (id == WindowEvent.WINDOW_GAINED_FOCUS ||
                        id == WindowEvent.WINDOW_LOST_FOCUS)));
    }
    
    /**
     * Invoked when {@link JXLayer#updateUI()} is called
     * from the {@code JXLayer} this {@code LayerUI} is set to.
     *
     * @param l the {@code JXLayer} which UI is updated
     */
    public void updateUI(JXLayer<V> l){
    }

    /**
     * {@inheritDoc}
     *
     * {@link JXLayer} manages its painting in a different way
     * so this method doesn't call the {@link #paint(Graphics, JComponent)} method
     * after background is filled for the opaque {@code JXLayer}s.
     */
    @Override
    public final void update(Graphics g, JComponent c) {
        if (c.isOpaque()) {
            g.setColor(c.getBackground());
            g.fillRect(0, 0, c.getWidth(), c.getHeight());
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final Accessible getAccessibleChild(JComponent c, int i) {
        return super.getAccessibleChild(c, i);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final int getAccessibleChildrenCount(JComponent c) {
        return super.getAccessibleChildrenCount(c);
    }
    
    /**
     * Returns the preferred size of the viewport for a view component.
     *
     * @return the preferred size of the viewport for a view component
     * @see Scrollable#getPreferredScrollableViewportSize() 
     */
    public Dimension getPreferredScrollableViewportSize(JXLayer<V> l) {
        if (l.getView() instanceof Scrollable) {
            return ((Scrollable)l.getView()).getPreferredScrollableViewportSize();
        }
        return l.getPreferredSize();
    }

    /**
      * Components that display logical rows or columns should compute
      * the scroll increment that will completely expose one block
      * of rows or columns, depending on the value of orientation.
      *
      * @return the "block" increment for scrolling in the specified direction
      * @see Scrollable#getScrollableBlockIncrement(Rectangle, int, int) 
      */
     public int getScrollableBlockIncrement(JXLayer<V> l, 
                                           Rectangle visibleRect, 
                                           int orientation, int direction) {
        if (l.getView() instanceof Scrollable) {
            return ((Scrollable)l.getView()).getScrollableBlockIncrement(
                    visibleRect,orientation, direction);
        }
        return (orientation == SwingConstants.VERTICAL) ? visibleRect.height :
            visibleRect.width;
    }

    /**
     * Returns false to indicate that the height of the viewport does not
     * determine the height of the layer, unless the preferred height
     * of the layer is smaller than the viewports height.  
     * 
     * @return whether the layer should track the height of the viewport
     * @see Scrollable#getScrollableTracksViewportHeight() 
     */
    public boolean getScrollableTracksViewportHeight(JXLayer<V> l) {
        if (l.getView() instanceof Scrollable) {
            return ((Scrollable)l.getView()).getScrollableTracksViewportHeight();
        }
        if (l.getParent() instanceof JViewport) {
            return (((JViewport)l.getParent()).getHeight() > l.getPreferredSize().height);
        }
        return false;
    }

    /**
     * Returns false to indicate that the width of the viewport does not
     * determine the width of the layer, unless the preferred width
     * of the layer is smaller than the viewports width. 
     * 
     * @return whether the layer should track the width of the viewport
     * @see Scrollable
     * @see LayerUI#getScrollableTracksViewportWidth(JXLayer) 
     */
    public boolean getScrollableTracksViewportWidth(JXLayer<V> l) {
        if (l.getView() instanceof Scrollable) {
            return ((Scrollable)l.getView()).getScrollableTracksViewportWidth();
        }
        if (l.getParent() instanceof JViewport) {
            return (((JViewport)l.getParent()).getWidth() > l.getPreferredSize().width);
        }
        return false;
    }

    /**
     * Components that display logical rows or columns should compute
     * the scroll increment that will completely expose one new row
     * or column, depending on the value of orientation.  Ideally,
     * components should handle a partially exposed row or column by
     * returning the distance required to completely expose the item.
     * <p>
     * Scrolling containers, like JScrollPane, will use this method
     * each time the user requests a unit scroll.
     *
     * @return The "unit" increment for scrolling in the specified direction.
     *         This value should always be positive.
     * @see Scrollable#getScrollableUnitIncrement(Rectangle, int, int) 
     */
    public int getScrollableUnitIncrement(JXLayer<V> l,
                                          Rectangle visibleRect, 
                                          int orientation, int direction) {
        if (l.getView() instanceof Scrollable) {
            return ((Scrollable)l.getView()).getScrollableUnitIncrement(
                    visibleRect, orientation, direction);
        }
        return 1;
    }
}
